Modèle de prédiction des prix du diamant¶

Importer les librairies python nécessaires¶

In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import joblib  
import os
from sklearn.linear_model import LinearRegression,Lasso,Ridge,ElasticNet
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from sklearn.preprocessing import StandardScaler, MinMaxScaler
from sklearn.model_selection import GridSearchCV
from sklearn.preprocessing import LabelEncoder
from scipy.stats import boxcox
from scipy.special import inv_boxcox

Importer la base de données¶

In [54]:
#lire la base de données
data=pd.read_csv('C:\\Users\\hj\\Downloads\\diamonds.csv')
print(f" data.shape:{data.shape}")
 data.shape:(53940, 11)
In [55]:
data.head()
Out[55]:
Unnamed: 0 carat cut color clarity depth table price x y z
0 1 0.23 Ideal E SI2 61.5 55.0 326 3.95 3.98 2.43
1 2 0.21 Premium E SI1 59.8 61.0 326 3.89 3.84 2.31
2 3 0.23 Good E VS1 56.9 65.0 327 4.05 4.07 2.31
3 4 0.29 Premium I VS2 62.4 58.0 334 4.20 4.23 2.63
4 5 0.31 Good J SI2 63.3 58.0 335 4.34 4.35 2.75
  • price : Il s’agit du prix du diamant en dollars américains. Il peut varier de 326 à 18,823, ce qui montre une grande variation en fonction des caractéristiques du diamant.
  • carat : Le poids du diamant, mesuré en carats. Un carat correspond à environ 0,2 gramme, et les valeurs de ce jeu de données vont de 0,2 à 5,01 carats. En général, plus le carat est élevé, plus le diamant est gros et coûteux, bien que cela dépende aussi des autres caractéristiques.
  • cut : La qualité de la taille du diamant, qui impacte son éclat et sa capacité à réfléchir la lumière. Les niveaux de qualité sont ordonnés de "Fair" (assez bon) à "Ideal" (idéal), avec "Good", "Very Good" et "Premium" entre les deux. Une meilleure taille maximise l'éclat du diamant.
  • color : La couleur du diamant, notée de J à D. D représente la meilleure qualité (diamant incolore), tandis que J est la qualité la plus basse (diamant légèrement coloré). Plus un diamant est incolore, plus il est rare et cher.
  • clarity : La pureté ou clarté du diamant, mesurée par la présence d'inclusions (impuretés internes) ou de défauts en surface. Les niveaux vont de I1 (inclut des inclusions visibles à l’œil nu) à IF (flawless ou sans défauts). Les niveaux intermédiaires, comme VS (Very Slightly Included) ou SI (Slightly Included), indiquent des niveaux de clarté entre ces deux extrêmes.
  • x : longueur en mm (0--10.74)
  • y : largeur en mm (0--58.9)
  • z : profondeur en mm (0--31.8)
  • depth : pourcentage de profondeur totale = z / moyenne(x, y) = 2 * z / (x + y) (43--79)
  • table : La largeur du sommet (table) du diamant par rapport à son point le plus large, exprimée en pourcentage. Une table idéale permet une bonne réflexion de la lumière sans trop de perte, typiquement entre 53 % et 58 %.

Analyse et manipulation des données¶

In [56]:
# Afficher la liste des  variables
data.columns
Out[56]:
Index(['Unnamed: 0', 'carat', 'cut', 'color', 'clarity', 'depth', 'table',
       'price', 'x', 'y', 'z'],
      dtype='object')
In [9]:
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 53940 entries, 0 to 53939
Data columns (total 11 columns):
 #   Column      Non-Null Count  Dtype  
---  ------      --------------  -----  
 0   Unnamed: 0  53940 non-null  int64  
 1   carat       53940 non-null  float64
 2   cut         53940 non-null  object 
 3   color       53940 non-null  object 
 4   clarity     53940 non-null  object 
 5   depth       53940 non-null  float64
 6   table       53940 non-null  float64
 7   price       53940 non-null  int64  
 8   x           53940 non-null  float64
 9   y           53940 non-null  float64
 10  z           53940 non-null  float64
dtypes: float64(6), int64(2), object(3)
memory usage: 4.5+ MB
  • On remarque q'on a 3 variables qualitatives: cut, color, clarity
  • 7 variables quantitatives: carat, depth, table, price, x, y, z
In [10]:
data.describe()
Out[10]:
Unnamed: 0 carat depth table price x y z
count 53940.000000 53940.000000 53940.000000 53940.000000 53940.000000 53940.000000 53940.000000 53940.000000
mean 26970.500000 0.797940 61.749405 57.457184 3932.799722 5.731157 5.734526 3.538734
std 15571.281097 0.474011 1.432621 2.234491 3989.439738 1.121761 1.142135 0.705699
min 1.000000 0.200000 43.000000 43.000000 326.000000 0.000000 0.000000 0.000000
25% 13485.750000 0.400000 61.000000 56.000000 950.000000 4.710000 4.720000 2.910000
50% 26970.500000 0.700000 61.800000 57.000000 2401.000000 5.700000 5.710000 3.530000
75% 40455.250000 1.040000 62.500000 59.000000 5324.250000 6.540000 6.540000 4.040000
max 53940.000000 5.010000 79.000000 95.000000 18823.000000 10.740000 58.900000 31.800000
In [57]:
data.duplicated().sum()
Out[57]:
np.int64(0)
In [4]:
sns.pairplot(data)
plt.show()
No description has been provided for this image
In [13]:
# Vérifier s'il y a des données manquantes
data.isna().sum()
Out[13]:
Unnamed: 0    0
carat         0
cut           0
color         0
clarity       0
depth         0
table         0
price         0
x             0
y             0
z             0
dtype: int64
In [14]:
# Vérifier s'il y a des données manquantes:visualisation graphique
plt.figure(figsize=(5,5))
sns.heatmap(data.isna())
Out[14]:
<Axes: >
No description has been provided for this image

Aucune données est manquantes

Data preproccessing¶

supprimer la colonnes Unnamed: 0¶
In [63]:
col_sup=["Unnamed: 0"] 

data=data.drop(col_sup,axis=1)   
data.head()
Out[63]:
carat cut color clarity depth table price x y z
0 0.23 Ideal E SI2 61.5 55.0 326 3.95 3.98 2.43
1 0.21 Premium E SI1 59.8 61.0 326 3.89 3.84 2.31
2 0.23 Good E VS1 56.9 65.0 327 4.05 4.07 2.31
3 0.29 Premium I VS2 62.4 58.0 334 4.20 4.23 2.63
4 0.31 Good J SI2 63.3 58.0 335 4.34 4.35 2.75
Encoding Categorical Variables¶
In [64]:
# Making a copy to keep original data in its form intact
dataA = data.copy()

# Applying label encoder to columns with categorical data
columns = ['cut','color','clarity']


mappings = {}

for col in columns:
    label_encoder = LabelEncoder()
    dataA[col] = label_encoder.fit_transform(dataA[col])
    joblib.dump(label_encoder, f"{col}_encoder.joblib")
    mappings[col] = dict(zip(label_encoder.classes_, label_encoder.transform(label_encoder.classes_)))

# Display mappings for each column
for col, mapping in mappings.items():
    print(f"Mappings for {col}: {mapping}")
dataA.head()
Mappings for cut: {'Fair': np.int64(0), 'Good': np.int64(1), 'Ideal': np.int64(2), 'Premium': np.int64(3), 'Very Good': np.int64(4)}
Mappings for color: {'D': np.int64(0), 'E': np.int64(1), 'F': np.int64(2), 'G': np.int64(3), 'H': np.int64(4), 'I': np.int64(5), 'J': np.int64(6)}
Mappings for clarity: {'I1': np.int64(0), 'IF': np.int64(1), 'SI1': np.int64(2), 'SI2': np.int64(3), 'VS1': np.int64(4), 'VS2': np.int64(5), 'VVS1': np.int64(6), 'VVS2': np.int64(7)}
Out[64]:
carat cut color clarity depth table price x y z
0 0.23 2 1 3 61.5 55.0 326 3.95 3.98 2.43
1 0.21 3 1 2 59.8 61.0 326 3.89 3.84 2.31
2 0.23 1 1 4 56.9 65.0 327 4.05 4.07 2.31
3 0.29 3 5 5 62.4 58.0 334 4.20 4.23 2.63
4 0.31 1 6 3 63.3 58.0 335 4.34 4.35 2.75
In [18]:
dataA.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 53940 entries, 0 to 53939
Data columns (total 10 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   carat    53940 non-null  float64
 1   cut      53940 non-null  int64  
 2   color    53940 non-null  int64  
 3   clarity  53940 non-null  int64  
 4   depth    53940 non-null  float64
 5   table    53940 non-null  float64
 6   price    53940 non-null  int64  
 7   x        53940 non-null  float64
 8   y        53940 non-null  float64
 9   z        53940 non-null  float64
dtypes: float64(6), int64(4)
memory usage: 4.1 MB
supprimer les données aberrantes¶
In [19]:
#Décrire les variables quantitative: identification des données aberrantes
quantitative_vars=['carat', 'depth', 'table', 'price', 'x', 'y', 'z']
for col in quantitative_vars:
    plt.figure(figsize=(3,3))
    plt.title(f"{col}")
    dataA[col].plot(kind='box')
    plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
In [65]:
## Supprimer les valeurs aberrantes de la variable "carat"

Q1 = dataA["carat"].quantile(0.25)
Q3 = dataA["carat"].quantile(0.75)
IQR = Q3 - Q1

LB = Q1 - 1.5 * IQR
UB = Q3 + 1.5 * IQR

data1 = dataA[(dataA["carat"] >= LB) & (dataA["carat"] <= UB)]
In [66]:
## Supprimer les valeurs aberrantes de la variable "depth"

Q1 = data1["depth"].quantile(0.25)
Q3 = data1["depth"].quantile(0.75)
IQR = Q3 - Q1

LB = Q1 - 1.5 * IQR
UB = Q3 + 1.5 * IQR

data2 = data1[(data1["depth"] >= LB) & (data1["depth"] <= UB)]
In [67]:
## Supprimer les valeurs aberrantes de la variable "table"

Q1 = data2["table"].quantile(0.25)
Q3 = data2["table"].quantile(0.75)
IQR = Q3 - Q1

LB = Q1 - 1.5 * IQR
UB = Q3 + 1.5 * IQR

data3 = data2[(data2["table"] >= LB) & (data2["table"] <= UB)]
In [68]:
## Supprimer les valeurs aberrantes de la variable "price"

Q1 = data3["price"].quantile(0.25)
Q3 = data3["price"].quantile(0.75)
IQR = Q3 - Q1

LB = Q1 - 1.5 * IQR
UB = Q3 + 1.5 * IQR

data4 = data3[(data3["price"] >= LB) & (data3["price"] <= UB)]
In [69]:
## Supprimer les valeurs aberrantes de la variable "x"

Q1 = data4["x"].quantile(0.25)
Q3 = data4["x"].quantile(0.75)
IQR = Q3 - Q1

LB = Q1 - 1.5 * IQR
UB = Q3 + 1.5 * IQR

data5 = data4[(data4["x"] >= LB) & (data4["x"] <= UB)]
In [70]:
## Supprimer les valeurs aberrantes de la variable "y"

Q1 = data5["y"].quantile(0.25)
Q3 = data5["y"].quantile(0.75)
IQR = Q3 - Q1

LB = Q1 - 1.5 * IQR
UB = Q3 + 1.5 * IQR

data6 = data5[(data5["y"] >= LB) & (data5["y"] <= UB)]
In [71]:
## Supprimer les valeurs aberrantes de la variable "z"

Q1 = data6["z"].quantile(0.25)
Q3 = data6["z"].quantile(0.75)
IQR = Q3 - Q1

LB = Q1 - 1.5 * IQR
UB = Q3 + 1.5 * IQR

data7 = data6[(data6["z"] >= LB) & (data6["z"] <= UB)]
In [72]:
data7.shape
Out[72]:
(46532, 10)
In [28]:
sns.pairplot(data7)
plt.show()
No description has been provided for this image
Matrice de correlation¶
In [29]:
plt.figure(figsize=(12, 8))
sns.heatmap(dataA.corr(), annot=True, cmap='coolwarm', fmt=".2f")
plt.title("Matrice de Correlation")
plt.show()
No description has been provided for this image

Analyse de la Matrice de Corrélation¶

Cette matrice de corrélation montre les relations linéaires entre les variables numériques de votre dataset. Voici quelques points clés de l'analyse :

  1. Variables fortement corrélées :

    • Les variables carat et price sont très fortement corrélées avec un coefficient de corrélation de 0.92, ce qui suggère que plus le poids du diamant (carat) est élevé, plus son prix (price) est susceptible d'être élevé.
    • Les dimensions x, y et z (longueur, largeur, et hauteur du diamant) ont des corrélations très élevées entre elles et aussi avec carat. Cela s'explique par le fait que les dimensions d'un diamant augmentent en fonction de son poids.
    • x, y, et z sont également fortement corrélées avec price, indiquant que des diamants de plus grandes dimensions tendent à avoir des prix plus élevés.
  2. Variables faiblement corrélées :

    • Les variables cut, color, et clarity ont des corrélations faibles avec price, ce qui signifie que ces caractéristiques influencent moins le prix par rapport au poids et aux dimensions du diamant.
    • La variable depth semble être faiblement corrélée avec toutes les autres variables, indiquant qu’elle n'a pas une influence significative sur les autres caractéristiques du diamant dans ce dataset.
  3. Corrélations négatives :

    • La variable cut a une faible corrélation négative avec depth et clarity, mais ces corrélations sont proches de zéro, donc peu significatives.

En résumé, les variables les plus significatives pour price dans ce dataset semblent être carat et les dimensions (x, y, z).

Entrainer les models¶

In [73]:
X = data7.drop('price', axis=1)
y = data7['price']
In [74]:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)
In [75]:
print(X_train.shape)
print(X_test.shape)
(32572, 9)
(13960, 9)
In [76]:
from sklearn.svm import SVR
from sklearn.linear_model import (
    LinearRegression, Ridge, Lasso, ElasticNet
)
from sklearn.metrics import r2_score
# Initialize the models
models = {
    'Linear Regression': LinearRegression(),
    'Ridge Regression': Ridge(),
    'LASSO Regression': Lasso(),
    'Elastic Net': ElasticNet(),
}

# Train and evaluate each model
for name, model in models.items():
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    score = r2_score(y_test, y_pred)
    print(f"{name} R^2 score: {score:.4f}")
    
Linear Regression R^2 score: 0.8906
Ridge Regression R^2 score: 0.8906
LASSO Regression R^2 score: 0.8903
Elastic Net R^2 score: 0.8154
C:\Users\hj\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\LocalCache\local-packages\Python311\site-packages\sklearn\linear_model\_coordinate_descent.py:697: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations, check the scale of the features or consider increasing regularisation. Duality gap: 2.568e+07, tolerance: 2.216e+07
  model = cd_fast.enet_coordinate_descent(
In [77]:
scaler = StandardScaler()
# Fit the scaler on the training data and transform both the training and testing data
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
In [78]:
# Train and evaluate each model
for name, model in models.items():
    model.fit(X_train_scaled, y_train)
    y_pred = model.predict(X_test_scaled)
    score = r2_score(y_test, y_pred)
    print(f"{name} R^2 score: {score:.4f}")
Linear Regression R^2 score: 0.8906
Ridge Regression R^2 score: 0.8906
C:\Users\hj\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\LocalCache\local-packages\Python311\site-packages\sklearn\linear_model\_coordinate_descent.py:697: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations, check the scale of the features or consider increasing regularisation. Duality gap: 8.560e+07, tolerance: 2.216e+07
  model = cd_fast.enet_coordinate_descent(
LASSO Regression R^2 score: 0.8904
Elastic Net R^2 score: 0.8426

Optimisation des hyper-paramètres par CV¶

Ridge¶

In [24]:
nb_va=300
# donne des valeurs entre 10^(-3) et 10^(2)
lambda_values=np.logspace(-3,2,nb_va)
lambda_range={"alpha":lambda_values}
model=Ridge()
grid=GridSearchCV(model,lambda_range,scoring="r2",cv=5)
grid.fit(X_train_scaled,y_train)
grid.best_params_
model=grid.best_estimator_
pred=model.predict(X_test_scaled)
R2_score=r2_score(pred,y_test)
print(R2_score)
0.8798556642592688

Elastic_Net¶

In [79]:
nb_val=50 # nbr de valeurs à tester 
lambda_range=np.logspace(-2,1,nb_val) # valeurs de lambda à tester
rho_range=[0.1,0.01,0.001]    # valeurs de rho à tester  
hpers={"alpha":lambda_range,"l1_ratio":rho_range} 
In [80]:
model_EN=ElasticNet(max_iter=100000,warm_start=True)
grid=GridSearchCV(model_EN,hpers,cv=5,scoring="r2",n_jobs=-1)
In [81]:
grid.fit(X_train_scaled,y_train)
Out[81]:
GridSearchCV(cv=5, estimator=ElasticNet(max_iter=100000, warm_start=True),
             n_jobs=-1,
             param_grid={'alpha': array([ 0.01      ,  0.01151395,  0.01325711,  0.01526418,  0.01757511,
        0.0202359 ,  0.02329952,  0.02682696,  0.03088844,  0.0355648 ,
        0.04094915,  0.04714866,  0.05428675,  0.06250552,  0.07196857,
        0.08286428,  0.09540955,  0.10985411,  0.12648552,  0.14563485,
        0.16768329,  0.19306977,  0.22229965,  0.25595479,  0.29470517,
        0.33932218,  0.39069399,  0.44984327,  0.51794747,  0.59636233,
        0.68664885,  0.79060432,  0.91029818,  1.04811313,  1.20679264,
        1.38949549,  1.59985872,  1.84206997,  2.12095089,  2.44205309,
        2.8117687 ,  3.23745754,  3.72759372,  4.29193426,  4.94171336,
        5.68986603,  6.55128557,  7.54312006,  8.68511374, 10.        ]),
                         'l1_ratio': [0.1, 0.01, 0.001]},
             scoring='r2')
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
GridSearchCV(cv=5, estimator=ElasticNet(max_iter=100000, warm_start=True),
             n_jobs=-1,
             param_grid={'alpha': array([ 0.01      ,  0.01151395,  0.01325711,  0.01526418,  0.01757511,
        0.0202359 ,  0.02329952,  0.02682696,  0.03088844,  0.0355648 ,
        0.04094915,  0.04714866,  0.05428675,  0.06250552,  0.07196857,
        0.08286428,  0.09540955,  0.10985411,  0.12648552,  0.14563485,
        0.16768329,  0.19306977,  0.22229965,  0.25595479,  0.29470517,
        0.33932218,  0.39069399,  0.44984327,  0.51794747,  0.59636233,
        0.68664885,  0.79060432,  0.91029818,  1.04811313,  1.20679264,
        1.38949549,  1.59985872,  1.84206997,  2.12095089,  2.44205309,
        2.8117687 ,  3.23745754,  3.72759372,  4.29193426,  4.94171336,
        5.68986603,  6.55128557,  7.54312006,  8.68511374, 10.        ]),
                         'l1_ratio': [0.1, 0.01, 0.001]},
             scoring='r2')
ElasticNet(alpha=np.float64(0.01), l1_ratio=0.1, max_iter=100000,
           warm_start=True)
ElasticNet(alpha=np.float64(0.01), l1_ratio=0.1, max_iter=100000,
           warm_start=True)
In [82]:
grid.best_params_
Out[82]:
{'alpha': np.float64(0.01), 'l1_ratio': 0.1}
In [83]:
model_EN=grid.best_estimator_
pred=model_EN.predict(X_test_scaled)
MSE=mean_squared_error(pred,y_test)
R2_score=r2_score(pred,y_test)
R2_score
Out[83]:
0.8694608572825874
In [84]:
joblib.dump(model_EN,'modelEN_Optimal.joblib')

joblib.dump(scaler,'scaler.joblib')
Out[84]:
['scaler.joblib']
In [ ]: